package org.bronycorps.mlptss.domain.model;

import java.util.ArrayList;
import java.util.List;

import org.bronycorps.mlptss.MlpTss;
import org.bronycorps.mlptss.domain.controller.GameController;
import org.bronycorps.mlptss.domain.model.event.BombEvent;
import org.bronycorps.mlptss.domain.model.event.BombEventListener;
import org.bronycorps.mlptss.domain.model.event.BreakShieldEvent;
import org.bronycorps.mlptss.domain.model.event.BreakShieldEventListener;
import org.bronycorps.mlptss.domain.model.event.CreateBossEvent;
import org.bronycorps.mlptss.domain.model.event.CreateBossEventListener;
import org.bronycorps.mlptss.domain.model.event.CreateSpecialMissileEvent;
import org.bronycorps.mlptss.domain.model.event.CreateSpecialMissileEventListener;
import org.bronycorps.mlptss.domain.model.event.ShootEvent;
import org.bronycorps.mlptss.domain.model.event.ShootEventListener;
import org.bronycorps.mlptss.domain.model.event.SpecialAttackEvent;
import org.bronycorps.mlptss.domain.model.event.SpecialAttackEventListener;
import org.bronycorps.mlptss.domain.model.event.StateChangeEvent;
import org.bronycorps.mlptss.domain.model.event.StateChangeEventListener;
import org.bronycorps.mlptss.domain.model.type.PlayerType;
import org.bronycorps.mlptss.manager.TrajectFactory;
import org.bronycorps.mlptss.manager.preferences.GameConfiguration;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;

public class Player extends AbstractActorModel {

	public enum State {
		DEAD, HIT, INVINCIBLE, NORMAL, WEAK
	}

	private Vector2 acceleration = new Vector2();

	private float angle = 0;

	private float bombCooldown;
	private List<BombEventListener> bombListeners = new ArrayList<BombEventListener>();
	private List<CreateBossEventListener> bossListeners = new ArrayList<CreateBossEventListener>();
	private List<BreakShieldEventListener> breakShieldListeners = new ArrayList<BreakShieldEventListener>();
	private final Character character;
	private int continues = 0;

	private GameConfiguration gameConfiguration;

	private GameController gameController;

	private int highscore = 0;

	private Rectangle hitbox = null;
	private int idPlayer;

	private int life;

	private Float maxBombCooldown;

	private float maxShootCooldown;

	private float maxSpecialAttackCharge = 1;

	private int nbBombs;

	private int nbMaxBombs;

	private PlayerType playerType;
	private float shootCooldown;
	private List<ShootEventListener> shootListeners = new ArrayList<ShootEventListener>();

	private float specialAttackCharge = 0;
	private List<CreateSpecialMissileEventListener> specialMissilesListeners = new ArrayList<CreateSpecialMissileEventListener>();
	private List<SpecialAttackEventListener> specialShootListeners = new ArrayList<SpecialAttackEventListener>();
	private State state = State.NORMAL;
	private List<StateChangeEventListener> stateChangeListeners = new ArrayList<StateChangeEventListener>();
	private float weakSpeedRatio;

	public Player(int idPlayer, Character character, PlayerType playerType) {
		super();

		this.gameController = GameController.getInstance();
		this.idPlayer = idPlayer;
		this.character = character;
		this.playerType = playerType;

		this.gameConfiguration = GameConfiguration.getInstance();
		this.life = this.gameConfiguration.getPlayerLife();
		this.maxShootCooldown = this.gameConfiguration.getPlayerCooldown();
		this.maxBombCooldown = this.gameConfiguration.getPlayerBombCooldown();
		this.weakSpeedRatio = this.gameConfiguration.getPlayerWeakSpeedRatio();
		this.nbBombs = this.gameConfiguration.getPlayerDefaultBombs();
		this.nbMaxBombs = this.gameConfiguration.getPlayerMaxBombs();

		this.maxSpeed = character.getSpeed();
		if (playerType == PlayerType.HUMAN) {
			this.continues = this.gameConfiguration.getNbContinues();
		}
	}

	public void addBomb() {
		if (this.nbBombs <= this.nbMaxBombs) {
			this.nbBombs++;
		}
	}

	public void addBonus(Bonus bonus) {
		if (bonus.isMoney()) {
			this.highscore += 10000;
		} else {
			switch (bonus.getBonusType()) {
			case BOMB:
				addBomb();
				break;
			case EXPLODE:
				fireBreakShieldEvent();
				break;
			default:
				break;
			}
		}
	}

	public void addHighscore(int points) {
		this.highscore = this.highscore + points;
	}

	public synchronized void addListener(BombEventListener listener) {
		this.bombListeners.add(listener);
	}

	public synchronized void addListener(BreakShieldEventListener listener) {
		this.breakShieldListeners.add(listener);
	}

	public synchronized void addListener(CreateBossEventListener listener) {
		this.bossListeners.add(listener);
	}

	public synchronized void addListener(
			CreateSpecialMissileEventListener listener) {
		this.specialMissilesListeners.add(listener);
	}

	public synchronized void addListener(ShootEventListener listener) {
		this.shootListeners.add(listener);
	}

	public synchronized void addListener(SpecialAttackEventListener listener) {
		this.specialShootListeners.add(listener);
	}

	public synchronized void addListener(StateChangeEventListener listener) {
		this.stateChangeListeners.add(listener);
	}

	public void addMaxSpecialAttackCharge(float charge) {
		this.maxSpecialAttackCharge = Math.min(3, this.maxSpecialAttackCharge
				+ charge);
	}

	public void addSpecialAttackCharge(float delta) {
		this.specialAttackCharge = Math.min(
				this.maxSpecialAttackCharge,
				this.specialAttackCharge + delta
						* this.character.getChargeSpeed());

	}

	public void fireBombEvent() {
		if (this.nbBombs > 0) {
			this.nbBombs--;
			if (this.bombCooldown == 0) {
				this.bombCooldown = this.maxBombCooldown;
				for (BombEventListener listener : this.bombListeners) {
					listener.onExplode(new BombEvent(this));
				}
			}
		}
	}

	public void fireBossEvent() {
		for (CreateBossEventListener listener : this.bossListeners) {
			listener.onCreateBoss(new CreateBossEvent(this));
		}

	}

	private void fireBreakShieldEvent() {
		for (BreakShieldEventListener listener : this.breakShieldListeners) {
			listener.onBreakShield(new BreakShieldEvent(this));
		}
	}

	public synchronized void fireShootEvent() {
		if (this.shootCooldown == 0) {
			this.shootCooldown = this.maxShootCooldown;
			for (ShootEventListener listener : this.shootListeners) {
				listener.onShoot(new ShootEvent(this));
			}
		}
	}

	public void fireSpecialAttackEvent() {
		for (SpecialAttackEventListener listener : this.specialShootListeners) {
			listener.onShoot(new SpecialAttackEvent(this));
		}
	}

	public void fireSpecialMissileEvent() {
		for (CreateSpecialMissileEventListener listener : this.specialMissilesListeners) {
			listener.onCreateSpecialMissile(new CreateSpecialMissileEvent(this,
					5));
		}

	}

	public synchronized void fireStateChangeEvent() {
		for (StateChangeEventListener listener : this.stateChangeListeners) {
			listener.onStateChange(new StateChangeEvent(this));
		}
	}

	public Vector2 getAcceleration() {
		return this.acceleration;
	}

	public float getAngle() {
		return this.angle;
	}

	public Character getCharacter() {
		return this.character;
	}

	public int getContinues() {
		return this.continues;
	}

	public int getHighscore() {
		return this.highscore;
	}

	public Rectangle getHitbox() {
		return this.hitbox;
	}

	public int getIdPlayer() {
		return this.idPlayer;
	}

	public int getLife() {
		return this.life;
	}

	public float getMaxShootCooldown() {
		return this.maxShootCooldown;
	}

	public float getMaxSpecialAttackCharge() {
		return this.maxSpecialAttackCharge;
	}

	@Override
	public float getMaxSpeed() {
		if (this.state == State.WEAK) {
			return super.getMaxSpeed() * this.weakSpeedRatio;
		} else {
			return super.getMaxSpeed();
		}
	}

	public int getNbBombs() {
		return this.nbBombs;
	}

	public PlayerType getPlayerType() {
		return this.playerType;
	}

	public float getSpecialAttackCharge() {
		return this.specialAttackCharge;
	}

	public State getState() {
		return this.state;
	}

	public void hit(Enemy model) {
		this.specialAttackCharge = 0;
		if (!this.gameController.isMatchOver()) {
			this.life--;
			if (this.life <= 0) {
				loseMatch();
			} else {
				this.state = State.HIT;
				TrajectFactory.getInstance().tweenKnockBackPlayer(this, model);
			}
			fireStateChangeEvent();
		}
	}

	public void hit(Missile model) {
		hitMissile();
	}

	public void hit(SpecialMissile model) {
		hitMissile();
	}

	private void hitMissile() {
		this.specialAttackCharge = 0;
		if (!this.gameController.isMatchOver()) {
			this.life -= 6;
			if (this.life <= 0) {
				loseMatch();
			} else {
				this.state = State.HIT;
				TrajectFactory.getInstance().tweenHittedPlayer(this);
			}
			fireStateChangeEvent();
		}
	}

	public void loseMatch() {
		Gdx.app.log(MlpTss.LOG, "Game over");
		this.state = State.DEAD;
		this.gameController.fireGameOver(this);

	}

	public void reduceCooldowns(float delta) {
		if (this.shootCooldown > 0) {
			this.shootCooldown = Math.max(0, this.shootCooldown - delta);
		}
		if (this.bombCooldown > 0) {
			this.bombCooldown = Math.max(0, this.bombCooldown - delta);
		}
	}

	public void removeContinue() {
		this.continues--;
	}

	public synchronized void removeListener(BombEventListener listener) {
		this.bombListeners.remove(listener);
	}

	public synchronized void removeListener(BreakShieldEventListener listener) {
		this.breakShieldListeners.remove(listener);
	}

	public synchronized void removeListener(CreateBossEventListener listener) {
		this.bossListeners.remove(listener);
	}

	public synchronized void removeListener(
			CreateSpecialMissileEventListener listener) {
		this.specialMissilesListeners.remove(listener);
	}

	public synchronized void removeListener(ShootEventListener listener) {
		this.shootListeners.remove(listener);
	}

	public synchronized void removeListener(SpecialAttackEventListener listener) {
		this.specialShootListeners.remove(listener);
	}

	public synchronized void removeListener(StateChangeEventListener listener) {
		this.stateChangeListeners.remove(listener);
	}

	public void reset() {
		this.state = State.NORMAL;
		restoreLife();
		this.nbBombs = GameConfiguration.getInstance().getPlayerDefaultBombs();
		this.maxSpecialAttackCharge = 1;
		this.specialAttackCharge = 0;
	}

	public void resetContinues() {
		this.continues = this.gameConfiguration.getNbContinues();
	}

	public void restoreLife() {
		this.life = GameConfiguration.getInstance().getPlayerLife();
		this.maxSpecialAttackCharge = 1;
	}

	public void setAcceleration(Vector2 acceleration) {
		this.acceleration = acceleration;
	}

	public void setAngle(float angle) {
		this.angle = angle;
	}

	public void setContinues(int continues) {
		this.continues = continues;
	}

	public void setHighscore(int highscore) {
		this.highscore = highscore;
	}

	public void setHitbox(Rectangle hitbox) {
		this.hitbox = hitbox;
	}

	public void setLife(int life) {
		this.life = life;
	}

	public void setMaxSpecialAttackCharge(float maxSpecialAttackCharge) {
		this.maxSpecialAttackCharge = maxSpecialAttackCharge;
	}

	public void setNbBombs(int nbBombs) {
		this.nbBombs = nbBombs;
	}

	public void setPlayerType(PlayerType playerType) {
		this.playerType = playerType;
	}

	public void setSpecialAttackCharge(float specialAttackCharge) {
		this.specialAttackCharge = specialAttackCharge;
	}

	public void setState(State state) {
		this.state = state;
		fireStateChangeEvent();
	}
}
